package com.xpec.c4.codec.h2o;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class H2oEncoder {
	private static final Log log = LogFactory.getLog(H2oEncoder.class);

	public static byte[] encodeToBytes(Object object) throws Exception {
		H2oBuffer buffer = new H2oBuffer();

		if (object == null) {
			H2oOutputStream.writeRawVarint(buffer, getClassType(null));
		} else {
			Class<?> clz = object.getClass();

			H2oOutputStream.writeRawVarint(buffer, getClassType(clz));

			encode(buffer, clz, object);
		}

		return buffer.toBytes();
	}

	public static ByteBuffer encodeToBuffer(Object object) throws Exception {
		byte[] bytes = encodeToBytes(object);

		ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
		buffer.put(bytes);
		buffer.flip();

		return buffer;
	}

	private static void encode(H2oBuffer buffer, Class<?> clz, Object object) throws Exception {
		if (clz == String.class) {
			H2oOutputStream.writeString(buffer, (String) object);
		} else if (clz == Boolean.class) {
			H2oOutputStream.writeBoolean(buffer, (Boolean) object);
		} else if (clz == Byte.class) {
			H2oOutputStream.writeByte(buffer, (Byte) object);
		} else if (clz == Character.class) {
			H2oOutputStream.writeNumber(buffer, (Character) object);
		} else if (clz == Short.class) {
			H2oOutputStream.writeNumber(buffer, (Short) object);
		} else if (clz == Integer.class) {
			H2oOutputStream.writeNumber(buffer, (Integer) object);
		} else if (clz == Long.class) {
			H2oOutputStream.writeNumber(buffer, (Long) object);
		} else if (clz == Float.class) {
			H2oOutputStream.writeFloat(buffer, (Float) object);
		} else if (clz == Double.class) {
			H2oOutputStream.writeDouble(buffer, (Double) object);
		} else if (clz.isEnum()) {
			encodeEnum(buffer, clz, object);
		} else if (clz.isArray()) {
			encodeArray(buffer, clz, object);
		} else if (object instanceof Collection) {
			encodeCollection(buffer, clz, object);
		} else if (object instanceof Map) {
			encodeMap(buffer, clz, object);
		} else if (object instanceof BigDecimal) {
			encodeBigDecimal(buffer, (BigDecimal) object);
		} else if (CodedInfo.classIngeterMapping.containsKey(clz)) {
			encodeCustom(buffer, clz, object);
		} else {
			throw new Exception("not define :" + clz.getName());
		}
	}

	//bigdecimal send by string
	private static void encodeBigDecimal(H2oBuffer buffer,BigDecimal bigDecimal) throws IOException
	{
		H2oOutputStream.writeString(buffer, (String) bigDecimal.toString());
	}
	
	
	private static void encodeEnum(H2oBuffer buffer, Class<?> clz, Object object) throws Exception {
		// 写入类标识
//		Integer num = CodedInfo.getRegisteredNumber(clz);
//		H2oOutputStream.writeRawVarint(buffer, num);

		H2oOutputStream.writeString(buffer, ((Enum<?>) object).name());
	}

	private static void encodeArray(H2oBuffer buffer, Class<?> clz, Object object) throws Exception {

		// 维度
		List<Integer> dimensionsList = new ArrayList<Integer>();

		// 计算维度
		Class<?> ctype = calArrayDimensions(0, dimensionsList, clz, object);
		// Class<?> ctype = clz;
		// while (ctype.isArray()) {
		// ctype = ctype.getComponentType();
		// }

		// 元素
		List<Object> elementList = new ArrayList<Object>();

		//计算元素下标
		fillArrayList(elementList, dimensionsList, 0, clz, object);

		// 写入长度
		int size = elementList.size();
		H2oOutputStream.writeRawVarint(buffer, size);
//		if (size == 0) {
//			return;
//		}

		// 写入类型
		int arrayType = getClassType(CodedInfo.primitiveToObject(ctype));
		H2oOutputStream.writeRawVarint(buffer, arrayType);
		// 写入类标识
		if (arrayType == CodedInfo.TYPE_CUSTOM) {
			Integer num = CodedInfo.getRegisteredNumber(ctype);
			H2oOutputStream.writeRawVarint(buffer, num);
		}

		// 写入维度
		H2oOutputStream.writeRawVarint(buffer, dimensionsList.size());
		for (Integer dimensions : dimensionsList) {
			H2oOutputStream.writeRawVarint(buffer, dimensions);
		}

		int realsize = 0;
		int position = buffer.position();
		 for (int i = 0; i < elementList.size(); i++) {
			 Object value = elementList.get(i);
			 if (value != null) {
				 ++realsize;
			
				 Class<?> subClz = value.getClass();
			
				 H2oOutputStream.writeTag(buffer, i, getClassType(subClz));
			
				 encode(buffer, subClz, value);
			 }
		 }

		H2oOutputStream.writeRawVarint(buffer, position, realsize);
	}

	private static void encodeCollection(H2oBuffer buffer, Class<?> clz, Object object) throws Exception {

		Collection<?> collection = (Collection<?>) object;

		//临时补丁
		Object[] array = null;
		if(collection.size() < 0){
			try{
				throw new Exception("collection is negative !!");
			}catch(Exception e){
				log.error(e, e);
			}
			array = new Object[0];
		}else{
			array = collection.toArray();
		}

		H2oOutputStream.writeRawVarint(buffer, array.length);

		if (array.length == 0) {
			return;
		}

		int realsize = 0;
		int position = buffer.position();
		for (int i = 0; i < array.length; i++) {
			if (array[i] != null) {
				++realsize;

				Class<?> subClz = array[i].getClass();

				H2oOutputStream.writeTag(buffer, i, getClassType(subClz));

				encode(buffer, subClz, array[i]);
			}
		}

		H2oOutputStream.writeRawVarint(buffer, position, realsize);
	}

	private static void encodeMap(H2oBuffer buffer, Class<?> clz, Object object) throws Exception {
		Map<?, ?> map = (Map<?, ?>) object;

		Set<?> set = map.keySet();

		int size = set.size();

		// map size
		H2oOutputStream.writeRawVarint(buffer, size);

		if (size == 0) {
			return;
		}

		Iterator<?> iter = set.iterator();
		while (iter.hasNext()) {
			Object key = iter.next();
			Object value = map.get(key);

			if (key == null) {
				H2oOutputStream.writeRawVarint(buffer, getClassType(null));
			} else {
				Class<?> subClz = key.getClass();

				// key type
				H2oOutputStream.writeRawVarint(buffer, getClassType(subClz));

				// key object
				encode(buffer, subClz, key);
			}

			if (value == null) {
				H2oOutputStream.writeRawVarint(buffer, getClassType(null));
			} else {
				Class<?> subClz = value.getClass();

				// value type
				H2oOutputStream.writeRawVarint(buffer, getClassType(subClz));

				// value object
				encode(buffer, subClz, value);
			}
		}
	}

	private static void encodeCustom(H2oBuffer buffer, Class<?> clz, Object object) throws Exception {
		// 写入类标识
		Integer num = CodedInfo.getRegisteredNumber(clz);
		H2oOutputStream.writeRawVarint(buffer, num);

		Field[] fields = CodedInfo.getClassFields(clz);

		int size = 0;
		int position = buffer.position();
		for (int i = 0; i < fields.length; i++) {
			Object fobj = fields[i].get(object);

			if (fobj != null) {
				Class<?> fieldClz = fobj.getClass();

				H2oOutputStream.writeTag(buffer, i, getClassType(fieldClz));

				encode(buffer, fieldClz, fobj);

				++size;
			}
		}

		// insert field
		H2oOutputStream.writeRawVarint(buffer, position, size);
	}

	private static int getClassType(Class<?> clz) throws Exception {
		Integer arrayType = CodedInfo.typeClassMapping.get(clz);
		if (arrayType != null) {
			return arrayType;
		} else {
			if (clz.isEnum()) {
				return CodedInfo.TYPE_ENUM;
			} else if (clz.isArray()) {
				return CodedInfo.TYPE_ARRAY;
			} else if (Collection.class.isAssignableFrom(clz)) {
				return CodedInfo.TYPE_COLLECTION;
			} else if (Map.class.isAssignableFrom(clz)) {
				return CodedInfo.TYPE_MAP;
			} else if (CodedInfo.classIngeterMapping.containsKey(clz)) {
				return CodedInfo.TYPE_CUSTOM;
			} else {
				throw new Exception("not define :" + clz.getName());
			}
		}
	}

	/**
	 * 计算维度，并返回数组类型
	 *
	 *  @param dimension
	 *  @param dimensionsList
	 *  @param clz
	 *  @param object
	 *  @return
	 */
	private static Class calArrayDimensions(int dimension, List<Integer> dimensionsList, Class clz, Object object) {
		if (clz.isArray()) {
			int size = Array.getLength(object);

			if (dimension == dimensionsList.size()) {
				dimensionsList.add(size);
			} else {
				Integer i = dimensionsList.get(dimension);
				if (size > i) {
					dimensionsList.remove(dimension);
					dimensionsList.add(dimension, size);
				}
			}

			Class ctype = clz.getComponentType();
			Class rtype = null;
			for (int i = 0; i < size; i++) {
				Object object2 = Array.get(object, i);
				rtype = calArrayDimensions(dimension + 1, dimensionsList, ctype, object2);
			}

			if (rtype == null) {
				return ctype;
			} else {
				return rtype;
			}
		} else {
			return null;
		}
	}

	/**
	 * 将空数据“填充进”多维数组，为了建立新的“下标”
	 *
	 *  @param elementList
	 *  @param dimensionsList
	 *  @param dimension
	 *  @param clz
	 *  @param object
	 */
	private static void fillArrayList(List<Object> elementList, List<Integer> dimensionsList, int dimension,
			Class clz, Object object) {
		if (object!=null&&clz.isArray()) {
			int dsize =  dimensionsList.get(dimension);
			int size = Array.getLength(object);
			
			Class ctype = clz.getComponentType();
			
			for (int i = 0; i < dsize; i++) {
				if(i >= size){
					fillArrayList(elementList, dimensionsList, dimension + 1, ctype, null);
				}else{
					Object object2 = Array.get(object, i);
					fillArrayList(elementList, dimensionsList, dimension + 1, ctype, object2);
				}
			}
			
		}else{
			if(clz.isArray()){
				int dsize =  dimensionsList.get(dimension);
				for (int i = 0; i < dsize; i++) {
					elementList.add(object);
				}
			}else{
				elementList.add(object);
			}
		}
	}
}
